package fudan.se.project.service.infrastructure.func.conference.resoure.paperalloc;

import fudan.se.project.domain.conference.Conference;
import fudan.se.project.domain.conference.production.Paper;
import fudan.se.project.domain.conference.production.ReviewInfo;
import fudan.se.project.domain.conference.role.PcMember;
import fudan.se.project.repository.entity.conference.production.ReviewInfoRepository;
import fudan.se.project.repository.entity.conference.role.PcMemberRepository;
import fudan.se.project.util.random.RandomUtils;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

@Slf4j
class PaperAllocatorByTopic extends AbstractPaperAllocator {


    private ReviewInfoRepository reviewInfoRepository;
    private PcMemberRepository pcMemberRepository;


    public PaperAllocatorByTopic(int assignedCount) {
        super(assignedCount);
    }

    public PaperAllocatorByTopic() {
    }


    void initialize(PcMemberRepository pcMemberRepository,
                    ReviewInfoRepository reviewInfoRepository) {
        this.pcMemberRepository = pcMemberRepository;
        this.reviewInfoRepository = reviewInfoRepository;
    }

    @Override
    protected boolean allocatePapers(Conference conference) {

        Set<Paper> papers = conference.getPapers();
        Set<AssignmentRecord> records = new HashSet<>();
        List<PcMember> allCandidates = pcMemberRepository.findAllCandidates(conference);

        AssignmentRecord record;
        for (Paper paper : papers) {
            List<PcMember> candidates = pcMemberRepository.findCandidateForPaperByTopicAndWriter(paper);
            if (candidates.size() >= assignedCount()) {
                record = randomAllocate(paper, candidates, assignedCount());
            } else {
                record = randomAllocate(paper, allCandidates, assignedCount());
            }
            if (record == null) return false;
            records.add(record);
        }

        Collection<ReviewInfo> reviewInfos = AssignmentRecord.aggregateTasks(records);
        reviewInfoRepository.saveAll(reviewInfos);

        return true;
    }

    private static AssignmentRecord randomAllocate(
            Paper paper,
            List<PcMember> pcMembers, int selected) {
        RandomUtils utils = new RandomUtils();

        Collection<Integer> randoms = utils.
                generateDistinctRandoms(selected, pcMembers.size());
        if (randoms.isEmpty()) return null;

        AssignmentRecord record = new AssignmentRecord(paper);
        for (Integer random : randoms) {
            record.addAssignee(pcMembers.get(random));
        }

        return record;
    }


    private static class AssignmentRecord {
        private final Paper paper;
        private final List<PcMember> assignees = new ArrayList<>();

        public AssignmentRecord(Paper paper) {
            this.paper = paper;
        }

        void addAssignee(PcMember pcMember) {
            this.assignees.add(pcMember);
        }

        void addAllAssignees(Collection<PcMember> pcMembers) {
            for (PcMember pcMember : pcMembers) {
                addAssignee(pcMember);
            }
        }

        Collection<ReviewInfo> assignTasks() {

            HashSet<ReviewInfo> hashSet = new HashSet<>();

            for (PcMember assignee : assignees) {
                hashSet.add(new ReviewInfo(paper, assignee));
            }

            return hashSet;
        }

        static Collection<ReviewInfo> aggregateTasks(Collection<AssignmentRecord> records) {
            HashSet<ReviewInfo> hashSet = new HashSet<>();
            for (AssignmentRecord record : records) {
                hashSet.addAll(record.assignTasks());
            }
            return hashSet;
        }
    }


}
